using System.Linq;
using System.Reflection;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Collections.Generic;
using log4net;

namespace Storage.EntityModels
{
    using Exceptions;

    #region BaseTripLoaDataContext

    /// <summary>
    /// This class represents the base class of the TripLoaDataContext;
    /// </summary>
    public partial class BaseTripLoaDataContext
    {
        #region DrivingInfo, custom Insert & Update

        partial void InsertDrivingInfo(DrivingInfo instance)
        {
            instance.OnInsert(this);
        }

        partial void UpdateDrivingInfo(DrivingInfo instance)
        {
            instance.OnUpdate(this);
        }

        #endregion

        #region Place, custom Insert & Update

        partial void InsertPlace(Place instance)
        {
            instance.OnInsert(this);
        }

        partial void UpdatePlace(Place instance)
        {
            instance.OnUpdate(this);    
        }

        #endregion

        #region Stage, custom Insert & Update

        partial void InsertStage(Stage instance)
        {
            instance.OnInsert(this);
        }

        partial void UpdateStage(Stage instance)
        {
            instance.OnUpdate(this);
        }

        #endregion

        #region User, custom Insert

        partial void InsertUser(User instance)
        {
            instance.OnInsert(this);
        }

        #endregion

        #region SearchTrips
        
        /// <summary>
        /// Search the db for the trips that satisfy some conditions.
        /// </summary>
        /// <param name="startLat">The latitude of the start point.</param>
        /// <param name="startLng">The longitude of the end point.</param>
        /// <param name="startTolerance">The tolerance on the start point.</param>
        /// <param name="endLat">The latitude of the end point.</param>
        /// <param name="endLng">The longitude of the end point.</param>
        /// <param name="endTolerance">The tolerance on the end point.</param>
        /// <returns></returns>
        [Function(Name = "dbo.SearchTrips", IsComposable = true)]
        public IQueryable<Trip> SearchTrips([Parameter(DbType = "Float")] System.Nullable<double> startLat, [Parameter(DbType = "Float")] System.Nullable<double> startLng, [Parameter(DbType = "Int")] System.Nullable<int> startTolerance, [Parameter(DbType = "Float")] System.Nullable<double> endLat, [Parameter(DbType = "Float")] System.Nullable<double> endLng, [Parameter(DbType = "Int")] System.Nullable<int> endTolerance)
        {
            return this.CreateMethodCallQuery<Trip>(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())), startLat, startLng, startTolerance, endLat, endLng, endTolerance, true, true);
        } 

        #endregion
    }

    #endregion

    #region TripLoaDataContext

    /// <summary>
    /// The DataContext of TripLoa.
    /// </summary>
    public partial class TripLoaDataContext : BaseTripLoaDataContext
    {
        #region Private fields: _tempRelationships, _customUpdates, _logger

        private Dictionary<KeyValuePair<int, int>, PrivacyLevel> _tempRelationships = new Dictionary<KeyValuePair<int, int>, PrivacyLevel>();

        private List<IPersistable> _customUpdates = new List<IPersistable>();

#if LOG
        private static readonly ILog _logger = LogManager.GetLogger(typeof(Stage));
#endif

        #endregion

        #region Public properties: TempRelationships

        /// <summary>
        /// Uses to track the temporary relations among the users.
        /// </summary>
        public Dictionary<KeyValuePair<int, int>, PrivacyLevel> TempRelationships
        {
            get { return _tempRelationships; }
        }

        #endregion

        #region Constructor

        /// <summary>
        /// Create a new instance of TripLoaDataContext.
        /// </summary>
        /// <param name="connection">The connection string.</param>
        internal TripLoaDataContext(string connection)
            : base(connection)
        {
        }

        #endregion

        #region Overriden methods: SubmitChanges

        /// <summary>
        /// Sends changes that were made to retrieved objects to the underlying database,
        /// and specifies the action to be taken if the submission fails.
        /// </summary>
        /// <param name="failureMode">The action to be taken if the submission fails. 
        /// Valid arguments are as follows:
        /// System.Data.Linq.ConflictMode.FailOnFirstConflict
        /// System.Data.Linq.ConflictMode.ContinueOnConflict
        /// </param>
        public override void SubmitChanges(ConflictMode failureMode)
        {
            var changeSet = GetChangeSet();
#if DEBUG
            var insertsAndUpdates = changeSet.Inserts.Union(changeSet.Updates);
            Validator.Validate(insertsAndUpdates);

            /* Check if one or more elements in ChangeSet is an istance of Stage;
               if yes, check if all other stages sharing a trip with the element in ChangeSet
               have consecutives values for Ordinal field. 
		    */
            var trips = insertsAndUpdates
                .OfType<Stage>()
                .Where(s => s.Trip != null)
                .Select(s => s.Trip)
                .Distinct();

		    // Ordinal checks.
            foreach (Trip trip in trips)
            {
                // NB: secondo me non serve l'ordinamento.
                /*var trip_stages_ordinal = from stage in trip.Stages.OrderBy(s => s.Ordinal)
                                          select stage.Ordinal;*/

                var trip_stages_ordinal = from stage in trip.Stages
                                          select stage.Ordinal;

                short ordinal = 1;
                while (ordinal <= trip_stages_ordinal.Count())
                {
                    if (!trip_stages_ordinal.Contains(ordinal))
                    {
                        // Check if the requested ordinal is in some new Stages.
                        // ASSERTION: at most one Trip is inserted. 
                        // If this condition is not satisfied then there is the possibility 
                        // that Stages with the same ordinal exist in different Trips.
                        if (!changeSet.Inserts
                            .OfType<Stage>()
                            .Any(s => s.Ordinal == ordinal))
                        {
                            throw new FieldNotValidException(string.Format("One or more Stages have a not " +
                            "valid value for Ordinal field: found {0} expected {1} [TripId: {2}].",
                            trip_stages_ordinal.ToArray()[ordinal - 1], ordinal, trip.Id));
                        }
                    }

                    ordinal++;
                }
            }
#endif
            /* The custom update query is not necessary if the framework has yet generated 
               an insert or update query. 
               It is also unnecessary if the entity is marked for removal. 
             */
            _customUpdates
                .RemoveAll(o => changeSet.Updates.Union(changeSet.Inserts).Union(changeSet.Deletes).Contains(o));

            base.SubmitChanges(failureMode);

            // Perform custom updates.
            _customUpdates.ForEach(o => o.OnUpdate(this));
        }
                
        #endregion

        #region Internal methods: AddToCustomUpdates
        
        /// <summary>
        /// Schedule an update query for an entity.
        /// Needed to track changes in geospatial data.
        /// </summary>
        /// <param name="o">The IPersistable to track.</param>
        internal void AddToCustomUpdates(IPersistable o)
        {
            /* The custom update query is not necessary if the framework has yet generated 
               an insert or update query. 
               It is also unnecessary if the entity is marked for removal. 
             */
            var changeSet = GetChangeSet();
            if (!changeSet.Updates.Union(changeSet.Inserts).Union(changeSet.Deletes).Contains(o))
            {
#if LOG
            _logger.Debug(string.Format("AddToCustomUpdates {0}", o.ToString()));
#endif
                if (!_customUpdates.Contains(o))
                    _customUpdates.Add(o);
            }
        } 

        #endregion
    } 

    #endregion
}
